package com.uobgroup.ews.uam.core.dao.ldap;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.AttributesMapperCallbackHandler;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.stereotype.Repository;

import com.uobgroup.ews.uam.core.configuration.AppConfig;
import com.uobgroup.ews.uam.core.domain.SecurityGroup;
import com.uobgroup.ews.uam.core.model.Group;
import com.uobgroup.ews.uam.core.model.Person;

@Repository("ldsDao")
public class LDSDaoImpl implements LDSDao {

   public static final String LDAP_FILTER_GROUP = "ldap_filter_group";
   public static final String LDAP_FILTER_PERSON = "ldap_filter_person";
   public static final String LDAP_DN_PERSON = "ldap_dn_person";
   public static final String LDAP_DN_GROUP = "ldap_dn_group";
   public static final String LDAP_DN_GROUP_ROLES = "ldap_dn_group_roles";
   public static final String LDAP_DN_GROUP_PROFILES = "ldap_dn_group_profile";
   public static final String LDAP_DN_GROUP_COMBINATIONKEY = "ldap_dn_group_combinationkey";
   public static final String LDAP_DN_GROUP_SEGMENT= "ldap_dn_group_segment";
   public static final String LDAP_DN_GROUP_ACCESSTOSTAFF = "ldap_dn_group_accesstostaff";
   public static final String LDAP_ATTRIBUTE_FIRSTNAME = "ldap_attr_firstname";
   public static final String LDAP_ATTRIBUTE_LASTNAME = "ldap_attr_lastname";
   public static final String LDAP_ATTRIBUTE_EMAIL = "ldap_attr_email";
   public static final String LDAP_ATTRIBUTE_ACCOUNTNAME = "ldap_attr_accountname";
   public static final String LDAP_ATTRIBUTE_COUNTRY = "ldap_attr_country";
   public static final String LDAP_ATTRIBUTE_SID = "ldap_attr_sid";
   
   public static final String LDS_BASE_DN = "ldap.lds.user.baseDN";

/*   private static final String USER_ACCOUNT_CONTROL_ATTR_NAME = "userAccountControl";
   private static final int FLAG_TO_DISABLE_USER = 0x2;
   private static final int USER_CONTROL_NORMAL_USER = 512;*/

   protected static final Logger logger = Logger.getLogger(LDSDaoImpl.class);

   private LdapTemplate ldsLdapTemplate;

   @Autowired
   public LDSDaoImpl(@Qualifier("ldsLdapTemplate") LdapTemplate ldsLdapTemplate) {
      this.ldsLdapTemplate = ldsLdapTemplate;
   }

   @SuppressWarnings("unchecked")
   public Person findUser(String username) {
      AppConfig.validate(LDAP_FILTER_PERSON);
      String baseDn = AppConfig.getProperty(LDAP_DN_PERSON);
      String filterPerson = AppConfig.getProperty(LDAP_FILTER_PERSON);

      AttributesMapper attributesMapper = new PersonAttributesMapper();
      AttributesMapperCallbackHandler handler = new AttributesMapperCallbackHandler(attributesMapper);

      CustomSearchExecutor searchExecutor = new CustomSearchExecutor();
      searchExecutor.setBaseDN(baseDn);
      searchExecutor.setFilter(filterPerson);
      searchExecutor.setParas(new String[]{username});

      ldsLdapTemplate.search(searchExecutor, handler);
      List<Person> queryResults = handler.getList();
      if (queryResults.isEmpty())
         return null;
      Person person = queryResults.get(0);
      return person;
   }

   @SuppressWarnings("unchecked")
   public Group findGroup(String groupName) {
      AppConfig.validate(LDAP_FILTER_GROUP);
      String baseDn = AppConfig.getProperty(LDAP_DN_GROUP);
      String filterGroup = AppConfig.getProperty(LDAP_FILTER_GROUP);
      
      AttributesMapper attributesMapper = new GroupAttributesMapper();
      AttributesMapperCallbackHandler handler = new AttributesMapperCallbackHandler(attributesMapper);

      CustomSearchExecutor searchExecutor = new CustomSearchExecutor();
      searchExecutor.setBaseDN(baseDn);
      searchExecutor.setFilter(filterGroup);
      searchExecutor.setParas(new String[]{groupName});

      ldsLdapTemplate.search(searchExecutor, handler);
      List<Group> queryResults = handler.getList();
      if (queryResults.isEmpty())
         return null;

      Group group = queryResults.get(0);
      return group;
   }
   
   @SuppressWarnings("unchecked")
   public Group findGroupByBaseDN(String groupName, String baseDn) {
      AppConfig.validate(LDAP_FILTER_GROUP);
      baseDn = AppConfig.getProperty(baseDn);
      String filterGroup = AppConfig.getProperty(LDAP_FILTER_GROUP);
      
      AttributesMapper attributesMapper = new GroupAttributesMapper();
      AttributesMapperCallbackHandler handler = new AttributesMapperCallbackHandler(attributesMapper);

      CustomSearchExecutor searchExecutor = new CustomSearchExecutor();
      searchExecutor.setBaseDN(baseDn);
      searchExecutor.setFilter(filterGroup);
      searchExecutor.setParas(new String[]{groupName});

      ldsLdapTemplate.search(searchExecutor, handler);
      List<Group> queryResults = handler.getList();
      if (queryResults.isEmpty())
         return null;

      Group group = queryResults.get(0);
      return group;
   }

   public boolean hasGroupByBaseDN(String roleName, String baseDN) {
      String sid = findGroupSIDByBaseDN(roleName, baseDN);
      if (sid == null || sid.trim().length() == 0)
         return false;
      return true;
   }

   @SuppressWarnings("unchecked")
   public String findGroupSIDByBaseDN(String roleName, String baseDN) {
      AppConfig.validate(LDAP_FILTER_GROUP);
      String filterGroup = AppConfig.getProperty(LDAP_FILTER_GROUP);
      
      AttributesMapper attributesMapper = new SidAttributesMapper();
      AttributesMapperCallbackHandler handler = new AttributesMapperCallbackHandler(attributesMapper);

      CustomSearchExecutor searchExecutor = new CustomSearchExecutor();
      searchExecutor.setBaseDN(baseDN);
      searchExecutor.setFilter(filterGroup);
      searchExecutor.setParas(new String[]{roleName});

      ldsLdapTemplate.search(searchExecutor, handler);
      List<String> queryResults = handler.getList();

      if (queryResults.isEmpty())
         return null;

      String sid = queryResults.get(0);
      return sid;
   }

   public String createUser(Person person) {
      validate();
      Attributes personAttributes = new BasicAttributes();
      personAttributes.put("objectclass", "person");
      personAttributes.put("objectclass", "userProxyFull");
      personAttributes.put(AppConfig.getProperty(LDAP_ATTRIBUTE_FIRSTNAME), person.getFirstName());
      personAttributes.put(AppConfig.getProperty(LDAP_ATTRIBUTE_LASTNAME), person.getLastName());
      personAttributes.put(AppConfig.getProperty(LDAP_ATTRIBUTE_EMAIL), person.getEmailAdress());
      personAttributes.put(AppConfig.getProperty(LDAP_ATTRIBUTE_ACCOUNTNAME), person.getAccountName());
      personAttributes.put(AppConfig.getProperty(LDAP_ATTRIBUTE_COUNTRY), person.getCountry());
      personAttributes.put(AppConfig.getProperty(LDAP_ATTRIBUTE_SID), person.getSid());

      DistinguishedName newUserDN = new DistinguishedName(userToDistinguishedName(person.getAccountName()));
      ldsLdapTemplate.bind(newUserDN, null, personAttributes);
      String sid = findUserSID(person.getAccountName());
      return sid;
   }

   public boolean addAttribute(Person person, String attName, String attValue){
      boolean hasSuccessful = true;
      try {
         ModificationItem repitem = new ModificationItem( DirContext.ADD_ATTRIBUTE, new BasicAttribute(attName,attValue));
         DistinguishedName userDN = new DistinguishedName(userToDistinguishedName(person.getAccountName()));
         ldsLdapTemplate.modifyAttributes( userDN, new ModificationItem[] {repitem} );
      } catch (RuntimeException exc){
         hasSuccessful = false;
         throw exc;
      }
      return hasSuccessful;
   }
   
   public boolean updateAttribute(Person person, String attName, String attValue){
      boolean hasSuccessful = true;
      try {
         ModificationItem repitem = new ModificationItem( DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(attName,attValue));
         DistinguishedName userDN = new DistinguishedName(userToDistinguishedName(person.getAccountName()));
         ldsLdapTemplate.modifyAttributes( userDN, new ModificationItem[] {repitem} );
      } catch (RuntimeException exc){
         hasSuccessful = false;
         throw exc;
      }
      return hasSuccessful;
   }

   public boolean deleteUser(Person person) {
      boolean hasSuccessful = true;
      try {
         DistinguishedName userDN = new DistinguishedName(userToDistinguishedName(person.getAccountName()));
         ldsLdapTemplate.unbind(userDN);
      } catch (RuntimeException exc){
         hasSuccessful = false;
         throw exc;
      }
      return hasSuccessful;
   }

   public String createGroup(SecurityGroup group) {
      validate();
      Attributes personAttributes = new BasicAttributes();
      personAttributes.put("objectclass", "group");
      personAttributes.put(AppConfig.getProperty(LDAP_ATTRIBUTE_ACCOUNTNAME), group.getDisplayName());
      DistinguishedName newGoupDN = new DistinguishedName(groupToDistinguishedName(group.getDisplayName(),group.getDN()));
      ldsLdapTemplate.bind(newGoupDN, null, personAttributes);
      String sid = findGroupSIDByBaseDN(group.getDisplayName(),group.getDN());
      return sid;
   }

   public boolean deleteGroup(SecurityGroup group) {
      boolean hasSuccessful = true;
      try {
         DistinguishedName groupDN = new DistinguishedName(groupToDistinguishedName(group.getDisplayName(),group.getDN()));
         ldsLdapTemplate.unbind(groupDN);
      } catch (RuntimeException exc){
         hasSuccessful = false;
         throw exc;
      }
      return hasSuccessful;
   }

   @SuppressWarnings("unchecked")
   public String findUserSID(String username) {
      AppConfig.validate(LDAP_FILTER_PERSON);
      String baseDN = AppConfig.getProperty(LDAP_DN_PERSON);
      String filterPerson = AppConfig.getProperty(LDAP_FILTER_PERSON);

      AttributesMapper attributesMapper = new SidAttributesMapper();
      AttributesMapperCallbackHandler handler = new AttributesMapperCallbackHandler(attributesMapper);

      CustomSearchExecutor searchExecutor = new CustomSearchExecutor();
      searchExecutor.setBaseDN(baseDN);
      searchExecutor.setFilter(filterPerson);
      searchExecutor.setParas(new String[]{username});

      ldsLdapTemplate.search(searchExecutor, handler);
      List<String> queryResults = handler.getList();
      if (queryResults.isEmpty())
         return null;
      String sid = queryResults.get(0);
      return sid;
   }

   public boolean hasAttribute(Person person, String attName) {
      Map<String, Object> attributes = getUserAttributes(person.getAccountName(), new String[]{attName});
      boolean hasAttr = attributes.containsKey(attName);
      return hasAttr;
   }

   @SuppressWarnings({"unchecked" })
   private Map<String, Object> getUserAttributes(String username, String[] attNames) {
      AppConfig.validate(LDAP_FILTER_PERSON);
      String baseDN = AppConfig.getProperty(LDAP_DN_PERSON);
      String filterPerson = AppConfig.getProperty(LDAP_FILTER_PERSON);

      MapAttributesMapper mapAttributesMapper = new MapAttributesMapper();
      mapAttributesMapper.setPropertyNames(attNames);
      AttributesMapperCallbackHandler handler = new AttributesMapperCallbackHandler(mapAttributesMapper);

      CustomSearchExecutor searchExecutor = new CustomSearchExecutor();
      searchExecutor.setBaseDN(baseDN);
      searchExecutor.setFilter(filterPerson);
      searchExecutor.setParas(new String[]{username});

      ldsLdapTemplate.search(searchExecutor, handler);
      List<Map<String, Object>> queryResults = handler.getList();
      if (queryResults.isEmpty())
         return null;

      Map<String, Object> map = queryResults.get(0);
      return map;
   }

   private String userToDistinguishedName(String username) {
      StringBuffer stringBuffer = new StringBuffer();
      stringBuffer.append("cn=").append(username);

      String baseUserDN = AppConfig.getProperty(LDAP_DN_PERSON);
      baseUserDN = (baseUserDN==null)?"":baseUserDN;

      if (!"".equals(baseUserDN))
         stringBuffer.append(",").append(baseUserDN);

      String distinguishedName = stringBuffer.toString();
      return distinguishedName;
   }

   private String groupToDistinguishedName(String rolename, String baseDN) {
      StringBuffer stringBuffer = new StringBuffer();
      stringBuffer.append("cn=").append(rolename);
      stringBuffer.append(",").append(baseDN);
      String distinguishedName = stringBuffer.toString();
      return distinguishedName;
   }

   private void validate() {
      AppConfig.validate(LDAP_ATTRIBUTE_FIRSTNAME);
      AppConfig.validate(LDAP_ATTRIBUTE_LASTNAME);
      AppConfig.validate(LDAP_ATTRIBUTE_EMAIL);
      AppConfig.validate(LDAP_ATTRIBUTE_ACCOUNTNAME);
      AppConfig.validate(LDAP_ATTRIBUTE_COUNTRY);
   }

   private class PersonAttributesMapper implements AttributesMapper {
      public Person mapFromAttributes(Attributes attributes) throws NamingException {
         Person person = new Person();
         Attribute attValue = attributes.get(AppConfig.getProperty(LDAP_ATTRIBUTE_FIRSTNAME));
         person.setFirstName(attValue == null ? null : attValue.get().toString());

         attValue = attributes.get(AppConfig.getProperty(LDAP_ATTRIBUTE_LASTNAME));
         person.setLastName(attValue == null ? null : attValue.get().toString());

         attValue = attributes.get(AppConfig.getProperty(LDAP_ATTRIBUTE_EMAIL));
         person.setEmailAdress(attValue == null ? null : attValue.get().toString());
         
         attValue = attributes.get(AppConfig.getProperty(LDAP_ATTRIBUTE_ACCOUNTNAME));
         person.setAccountName(attValue == null ? null : attValue.get().toString());
         
         attValue = attributes.get(AppConfig.getProperty(LDAP_ATTRIBUTE_COUNTRY));
         person.setCountry(attValue == null ? null : attValue.get().toString());
         return person;
      }
   }

   private class GroupAttributesMapper implements AttributesMapper {
      public Group mapFromAttributes(Attributes attributes) throws NamingException {
         Group group = new Group();
         Attribute attValue = attributes.get(AppConfig.getProperty(LDAP_ATTRIBUTE_ACCOUNTNAME));
         group.setName((attValue == null ? null : attValue.get().toString()));

         attValue = attributes.get(AppConfig.getProperty(LDAP_ATTRIBUTE_COUNTRY));
         group.setCountry(attValue == null ? null : attValue.get().toString());
         return group;
      }
   }

   private class SidAttributesMapper implements AttributesMapper {
      public String mapFromAttributes(Attributes attributes) throws NamingException {
         Object attValue = attributes.get(AppConfig.getProperty(LDAP_ATTRIBUTE_SID)).get();
         String sid = attValue == null ? null : LdapUtils.convertBinarySidToString((byte[])attValue);;
         return sid;
      }
   }

   private class MapAttributesMapper implements AttributesMapper {
      private String[] propertyNames;

      public Map<String, Object> mapFromAttributes(Attributes attributes) throws NamingException {
         Map<String, Object> maps = new HashMap<String, Object>();
         if (propertyNames != null){
            for (String propertyName : propertyNames){
               Attribute attValue = attributes.get(propertyName);
               if (attValue != null){
                  maps.put(propertyName, attValue.get());
               }
            }
         }
         return maps;
      }
      
      public void setPropertyNames(String[] propertyNames){
         this.propertyNames = propertyNames;
      }
   }

   @Override
   public void assignChildGroupToParent(SecurityGroup parent, SecurityGroup child) {
      String parentDn = groupToDistinguishedName(parent.getDisplayName(), parent.getDN());
      String childDn = groupToDistinguishedName(child.getDisplayName(), child.getDN());
      String childDnFull = childDn + "," + AppConfig.getProperty(LDS_BASE_DN);
      ModificationItem[] mods = new ModificationItem[] {new ModificationItem(DirContext.ADD_ATTRIBUTE,
            new BasicAttribute("member", childDnFull)) };
      ldsLdapTemplate.modifyAttributes(parentDn, mods);
   }

   @Override
   public void unassignChildGroupFromParent(SecurityGroup parent, SecurityGroup child) {
      String parentDn = groupToDistinguishedName(parent.getDisplayName(), parent.getDN());
      String childDn = groupToDistinguishedName(child.getDisplayName(), child.getDN());
      String childDnFull = childDn + "," + AppConfig.getProperty(LDS_BASE_DN);
      ModificationItem[] mods = new ModificationItem[] {new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
            new BasicAttribute("member", childDnFull)) };
      ldsLdapTemplate.modifyAttributes(parentDn, mods);
   }

   @Override
   public void assignUserToProfile(SecurityGroup parent, SecurityGroup child) {
      assignChildGroupToParent(parent, child);
      String childDn = groupToDistinguishedName(child.getDisplayName(), child.getDN());
      ModificationItem[] mods = new ModificationItem[] {new ModificationItem(DirContext.ADD_ATTRIBUTE,
            new BasicAttribute("Profile", parent.getDisplayName())) };
      ldsLdapTemplate.modifyAttributes(childDn, mods);
   }

   @Override
   public void unassignUserFromProfile(SecurityGroup parent, SecurityGroup child) {
      unassignChildGroupFromParent(parent, child);
      String childDn = groupToDistinguishedName(child.getDisplayName(), child.getDN());
      ModificationItem[] mods = new ModificationItem[] {new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
            new BasicAttribute("Profile", parent.getDisplayName())) };
      ldsLdapTemplate.modifyAttributes(childDn, mods);
   }
   
   
}
